package com.hongding.sealcenter.helper.cert;

import cn.com.hongding.seal.sign.util.CertUtils;
import cn.hutool.core.util.RandomUtil;
import com.hongding.sealcenter.enums.AlgorithmOIDEnum;
import com.hongding.sealcenter.enums.SignAlgEnum;
import com.hongding.sealcenter.helper.CAHelper;
import com.hongding.sealcenter.util.CertificateUtils;
import com.hongding.sealcenter.util.PfxCertUtil;
import com.hongding.sealcenter.vo.CACertificateVO;
import com.hongding.sealcenter.vo.PkcsVO;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.springframework.stereotype.Service;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.URL;
import java.security.*;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;

@Slf4j
@Service("zjca" + CAHelper.BASE_NAME)
public class ZJCAHelper implements CAHelper {
    private final Map<String, CertUtils.KeyStoreInfo> pfxBuild = new HashMap<>();


    /**
     * 申请证书
     * @param caCertificateVO 获取证书信息
     * @param password 证书密码
     * @return
     * @throws RuntimeException
     */
    @Override
    public PkcsVO applyCertificate(CACertificateVO caCertificateVO, String password) throws RuntimeException {
        try {
            PKCS8EncodedKeySpec pkcs8 = CertificateUtils.getPkcs8EncodedKeySpec(caCertificateVO.getSignAlgEnum());
            PKCS10CertificationRequest pkcs10 = CertificateUtils.getPKCS10CertificationRequest(caCertificateVO.getDn(), caCertificateVO.getSignAlgEnum(), pkcs8);
            CertUtils.KeyStoreInfo issuerKeyPair = getRootCert(caCertificateVO.getSignAlgEnum());
            boolean isCA = false;
            X509Certificate x509Certificate = makeCertificate(isCA, pkcs10.getSubjectPublicKeyInfo(), caCertificateVO, issuerKeyPair);
            log.info("证书序列号：{}", x509Certificate.getSerialNumber().toString(16));
            log.info("证书颁发者：{}", x509Certificate.getIssuerDN().toString());
            log.info("证书所有者：{}", x509Certificate.getSubjectDN().toString());


            String pkcs8Str = Base64.getEncoder().encodeToString(pkcs8.getEncoded());
            // 证书
            String certStr = Base64.getEncoder().encodeToString(x509Certificate.getEncoded());
            String pkcs12 = CertificateUtils.getPkcs12(caCertificateVO.getSignAlgEnum(), pkcs8Str, certStr, password);


            /*****此处往下是验证证书是否能正常使用代码*****/
            /*//1.从pfx文件中获取 KeyStore 对象
            KeyStore keyStore = PfxCertUtil.getKeyStore(pkcs12, password);

            //获取（证书别名）证书序列号
            String alias =keyStore.aliases().nextElement();

            //获取私钥
            PrivateKey privateKey = PfxCertUtil.getPrivateKey(keyStore, alias, password);

            //获取证书
            X509Certificate cert = PfxCertUtil.getCert(keyStore, alias);

            //获取签名算法名称
            String sigAlgName = cert.getSigAlgName();

            //获取公钥
            PublicKey publicKey = PfxCertUtil.getPublicKey(cert);

            //2.使用私钥进行签名
            String text = "Hello, world!";//待签名数据
            String signStr = PfxCertUtil.pfxSign(privateKey, sigAlgName, text);
            log.info("签名值：{}", signStr);

            //3.使用公钥验证签名
            Boolean verified = PfxCertUtil.verifySign(publicKey, sigAlgName, text, signStr);
            log.info("验证结果：{}", verified);*/
            /*****此处往上是验证证书是否能正常使用代码*****/

            return  new PkcsVO(pkcs8Str, Base64.getEncoder().encodeToString(pkcs10.getEncoded()),pkcs12);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 构造证书信息
     * @param isCA 是否是CA证书
     * @param publicKeyInfo pkcs10
     * @param vo 证书信息
     * @param issuerKeyPair root证书信息
     */
    public X509Certificate makeCertificate(boolean isCA, SubjectPublicKeyInfo publicKeyInfo,
                                           CACertificateVO vo, CertUtils.KeyStoreInfo issuerKeyPair)
            throws Exception {
        X500Name subject = buildSubject(vo.getApplyName(), vo.getPhone(), vo.getEmail(), vo.getUnit(),vo.getApplyCardNo());
        long tempNum = (long)vo.getCertDay();
        long certExpire = tempNum * 24 * 60 * 60 * 1000;
        PrivateKey issPri = issuerKeyPair.getPrivateKey();
        PublicKey issPub = issuerKeyPair.getCertificate().getPublicKey();
        X500Name issuer = getIssuer();
        BigInteger identifier = incrementAndGet();
        X509v3CertificateBuilder v3CertGen = new X509v3CertificateBuilder(issuer, identifier,
                new Date(System.currentTimeMillis()), new Date(System.currentTimeMillis() + certExpire),
                subject, publicKeyInfo);
        if (isCA) {
            v3CertGen.addExtension(Extension.basicConstraints, false, new BasicConstraints(true));
            v3CertGen.addExtension(Extension.keyUsage, false, new KeyUsage(KeyUsage.keyAgreement | KeyUsage.keyCertSign | KeyUsage.cRLSign | KeyUsage.encipherOnly));
        } else {
            v3CertGen.addExtension(Extension.keyUsage, false, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.nonRepudiation | KeyUsage.keyEncipherment | KeyUsage.dataEncipherment));
            v3CertGen.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(KeyPurposeId.id_kp_clientAuth));
        }
        AlgorithmOIDEnum signAlgo = AlgorithmOIDEnum.buildSignAlg(vo.getSignAlgEnum());
        JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder(signAlgo.getCode());
        contentSignerBuilder.setProvider(BouncyCastleProvider.PROVIDER_NAME);
        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME)
                .getCertificate(v3CertGen.build(contentSignerBuilder.build(issPri)));
        cert.checkValidity(new Date());
        cert.verify(issPub);
        return cert;
    }

    /**
     * 获取根证书
     */
    private CertUtils.KeyStoreInfo getRootCert(SignAlgEnum signAlgEnum) throws Exception {
        if (pfxBuild.containsKey(signAlgEnum.getCode())) {
            return pfxBuild.get(signAlgEnum.getCode());
        }
        String certPassword = "!@#!214QAZqwe";
        URL sm2CertUrl = ZJCAHelper.class.getClassLoader().getResource("cert/" + signAlgEnum.getCode() + ".pfx");
        if (Objects.isNull(sm2CertUrl)) {
            throw new RuntimeException("root证书不存在");
        }
        CertUtils.KeyStoreInfo rootCert = CertUtils.parseKeyStore(new File(sm2CertUrl.getPath()), certPassword);
        pfxBuild.put(signAlgEnum.getCode(), rootCert);
        return rootCert;
    }
    /**
     * 默认构建帧证书头部信息
     * @param name 名称
     * @param mobile 联系方式
     * @param email 邮箱
     * @param unitName 单位名称
     * @param cardNo 证件号码
     */
    public X500Name buildSubject(String name, String mobile, String email, String unitName,String cardNo){
        X500NameBuilder subDNBuilder = new X500NameBuilder(BCStyle.INSTANCE);
        subDNBuilder.addRDN(BCStyle.CN, name); // 名称 用户名称/单位名称
        subDNBuilder.addRDN(BCStyle.C, "CN"); // 国家代码 固定值CN
        subDNBuilder.addRDN(BCStyle.O, unitName); // 组织单位名称
        subDNBuilder.addRDN(BCStyle.SERIALNUMBER, cardNo); // 证件号码
        subDNBuilder.addRDN(BCStyle.E, email); // 邮件
        subDNBuilder.addRDN(BCStyle.TELEPHONE_NUMBER, mobile); // 电话号码
        return subDNBuilder.build();
    }

    /**
     * 证书颁发者信息
     */
    public  X500Name getIssuer(){
        X500NameBuilder subDNBuilder = new X500NameBuilder(BCStyle.INSTANCE);
        subDNBuilder.addRDN(BCStyle.CN, "自建证书中心");
        subDNBuilder.addRDN(BCStyle.C, "CN");
        return subDNBuilder.build();
    }

    /**
     * 生成随机数
     * @return 随机数
     */
    public BigInteger incrementAndGet(){
        long l = System.currentTimeMillis();
        int r1 = RandomUtil.getRandom().nextInt(100000000, 999999999);
        int r2 = RandomUtil.getRandom().nextInt(100000000, 999999999);
        return new BigInteger(String.valueOf(l) + r1 + r2 + 0);
    }
}
